package org.mm.proxycache.util;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.mina.common.IoSession;
import org.mm.proxycache.http.HttpRequest;
import org.mm.proxycache.http.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.qos.logback.access.spi.BasicContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.BasicStatusManager;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.spi.AppenderAttachable;
import ch.qos.logback.core.spi.AppenderAttachableImpl;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.FilterAttachableImpl;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.status.StatusManager;
import ch.qos.logback.core.util.StatusPrinter;

import com.google.common.base.Preconditions;
import com.google.inject.Singleton;

@Singleton
public class AccessLogger implements Context, AppenderAttachable<AccessEvent>, FilterAttachable
{
    AppenderAttachableImpl<AccessEvent> impl = new AppenderAttachableImpl<AccessEvent>();

    // Attributes from ContextBase:
    private String name;
    StatusManager sm = new BasicStatusManager();

    Map<String, String> propertyMap = new HashMap<String, String>();
    Map<String, Object> objectMap = new HashMap<String, Object>();
    private FilterAttachableImpl fai = new FilterAttachableImpl();

    String filename;
    AtomicBoolean started = new AtomicBoolean();

    public AccessLogger()
    {
        // log.debug( "Created AccessLogger" );
        // putObject( CoreGlobal.EVALUATOR_MAP, new HashMap<Object, Object>() );
        //
        // start();

        // RollingFileAppender<AccessEvent> appender = new
        // RollingFileAppender<AccessEvent>();
        // appender.setFile( "log/access.log" );
        // appender.setLayout( new AccessLayout() );
        // appender.setContext( new BasicContext() );
        //
        // TimeBasedRollingPolicy policy = new TimeBasedRollingPolicy();
        // policy.setFileNamePattern( "log/access.log.%d.gz" );
        // policy.setParent( appender );
        // // policy.setContext( appender.getContext() );
        // appender.setRollingPolicy( policy );
        // policy.start();
        //

//        RollingFileAppender<AccessEvent> appender = new RollingFileAppender<AccessEvent>();
//        appender.setFile( "log/access.log" );
//        appender.setLayout( new AccessLayout() );
//        appender.setContext( new BasicContext() );
//
//        FixedWindowRollingPolicy policy = new FixedWindowRollingPolicy();
//        policy.setFileNamePattern( "log/access.%i.log" );
//        policy.setMinIndex( 1 );
//        policy.setMaxIndex( 3 );
//        policy.setContext( appender.getContext() );
//
//        policy.setParent( appender );
//        policy.start();
//        //
//        SizeBasedTriggeringPolicy trigger = new SizeBasedTriggeringPolicy();
//        trigger.setMaxFileSize( "10MB" );
//        trigger.setContext( appender.getContext() );
//        trigger.start();
//        appender.setTriggeringPolicy( trigger );
        
        
        FileAppender<AccessEvent> appender = new FileAppender<AccessEvent>();
        appender.setFile( "log/access.log" );
        appender.setLayout( new AccessLayout() );
        appender.setContext( new BasicContext() );

        appender.start();
        impl.addAppender( appender );
    }

    public void start()
    {
        if ( filename == null )
        {
            filename = "logback-access.xml";
        }

        File configFile = new File( filename );
        if ( configFile.exists() )
        {
            try
            {
                JoranConfigurator jc = new JoranConfigurator();
                jc.setContext( this );
                jc.doConfigure( filename );
            }
            catch (JoranException e)
            {
                StatusPrinter.print( getStatusManager() );
                log.error( "Error reading {}", filename );
            }
        }
        else
        {
            log.error( "[{}] does not exist", filename );
            return;
        }

        started.set( true );
        log.debug( "started" );
    }

    public void stop()
    {
        started.set( false );
    }

    public void log( AccessType type, HttpRequest request, HttpResponse response,
            IoSession clientSession, IoSession serverSession )
    {
        checkNotNull( type );
        checkNotNull( request );
        checkNotNull( response );
        checkNotNull( clientSession );

        AccessEvent event = new AccessEvent( type, request, response, clientSession, serverSession );
        impl.appendLoopOnAppenders( event );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#getName()
     */
    public String getName()
    {
        return name;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#getObject(java.lang.String)
     */
    public Object getObject( String key )
    {
        return objectMap.get( key );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#getProperty(java.lang.String)
     */
    public String getProperty( String key )
    {
        return propertyMap.get( key );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#getPropertyMap()
     */
    public Map<String, String> getPropertyMap()
    {
        return propertyMap;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#getStatusManager()
     */
    public StatusManager getStatusManager()
    {
        return sm;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#putObject(java.lang.String,
     *      java.lang.Object)
     */
    public void putObject( String key, Object value )
    {
        objectMap.put( key, value );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#setName(java.lang.String)
     */
    public void setName( String name )
    {
        Preconditions.checkArgument( this.name == null, "Name is already set." );
        this.name = name;
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.Context#setProperty(java.lang.String,
     *      java.lang.String)
     */
    public void setProperty( String key, String value )
    {
        propertyMap.put( key, value );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#addAppender(ch.qos.logback.core.Appender)
     */
    public void addAppender( Appender<AccessEvent> newAppender )
    {
        impl.addAppender( newAppender );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#detachAndStopAllAppenders()
     */
    public void detachAndStopAllAppenders()
    {
        impl.detachAndStopAllAppenders();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#detachAppender(ch.qos.logback.core.Appender)
     */
    public boolean detachAppender( Appender<AccessEvent> appender )
    {
        return impl.detachAppender( appender );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#detachAppender(java.lang.String)
     */
    public Appender<AccessEvent> detachAppender( String name )
    {
        return impl.detachAppender( name );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#getAppender(java.lang.String)
     */
    public Appender<AccessEvent> getAppender( String name )
    {
        return impl.getAppender( name );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#isAttached(ch.qos.logback.core.Appender)
     */
    public boolean isAttached( Appender<AccessEvent> appender )
    {
        return impl.isAttached( appender );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.AppenderAttachable#iteratorForAppenders()
     */
    @SuppressWarnings("unchecked")
    public Iterator iteratorForAppenders()
    {
        return impl.iteratorForAppenders();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.FilterAttachable#addFilter(ch.qos.logback.core.filter.Filter)
     */
    public void addFilter( Filter newFilter )
    {
        fai.addFilter( newFilter );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.FilterAttachable#clearAllFilters()
     */
    public void clearAllFilters()
    {
        fai.clearAllFilters();
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.FilterAttachable#getFilterChainDecision(java.lang.Object)
     */
    public FilterReply getFilterChainDecision( Object event )
    {
        return fai.getFilterChainDecision( event );
    }

    /*
     * (non-Javadoc)
     * 
     * @see ch.qos.logback.core.spi.FilterAttachable#getFirstFilter()
     */
    public Filter getFirstFilter()
    {
        return fai.getFirstFilter();
    }

    private static final Logger log = LoggerFactory.getLogger( AccessLogger.class );
}
